package org.springframework.web.servlet.ajax.json;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.context.ApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;

/**
 * This DispatcherServlet sets up the AjaxContext, assists in maintaining flash
 * storage (@see AjaxContext), and writes JSON while preventing standard view
 * rendering when appropriate.
 * <p>
 * Subclassing the DispatcherServlet was not taken lightly. Setting up the
 * AjaxContext in a servlet Filter doesn't work because there's no access to the
 * child servlet applicationContext, which is where the PartialsCore should be
 * configured. And setting up the AjaxContext in a HandlerInterceptor is not a
 * good idea because, although it can access the child applicationContext, an
 * interceptor is only invoked if the corresponding HandlerMapping is being
 * used. So that solution would leave open the possiblity of a request where the
 * context is not set up.
 * 
 * @author Charles Tassoni
 * 
 */
public class AjaxReadyDispatcherServlet extends DispatcherServlet{
	private PartialsCore partialsCore;
	
	@Override
	protected void initStrategies(ApplicationContext context) {
		super.initStrategies(context);
		this.partialsCore = (PartialsCore) context.getBean("partialsCore");
	}
	
	@Override
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		handleFlash(request);
		
		JsonAjax jsonAjax = makeJsonAjax();
		AjaxContext.setup(jsonAjax, request);
		try{
			super.doDispatch(request, response);
		}finally{
			AjaxContext.clear();
		}
	}
	
	@Override
	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		//TODO document jsonredirect: and make that string a constant.
		if(mv != null && mv.getViewName() != null && mv.getViewName().startsWith("jsonredirect:")){
			String redirectURL =  mv.getViewName().substring(("jsonredirect:".length()));
			partialsCore.writeJsonRedirect(redirectURL, request, response);
		}
		else if(AjaxContext.getJson().getAllJsonWork() != null && !AjaxContext.getJson().getAllJsonWork().isEmpty()){
			JsonAjaxReaction reaction = this.makeJsonAjaxReaction();
			
			reaction.setPartialsCore(partialsCore);
			reaction.setModel(mv.getModel());
			reaction.setRequest(request);
			reaction.setResponse(response);
			reaction.buildJson(AjaxContext.getJson().getAllJsonWork());
			reaction.writeJson();
		}
		else
			super.render(mv, request, response);
	}
	
	/**
	 * TODO As far as I know, at this writing Spring MVC doesn't have a flash scope,
	 * although WebFlow does.  Will switch to Spring MVC flash if it becomes available.
	 */
	protected void handleFlash(HttpServletRequest request){
		HttpSession session = request.getSession(false);
		if(session == null) return;
		Map map = (Map) session.getAttribute(AjaxContext.FLASH);
		if(map == null){
			map = new HashMap();
			session.setAttribute(AjaxContext.FLASH, map);
			Set doNotClearThisTime = new HashSet();
			map.put(AjaxContext.FLASH_DO_NOT_CLEAR_THIS_TIME, doNotClearThisTime);
			return;
		}
		Set doNotClearThisTime = (Set) map.get(AjaxContext.FLASH_DO_NOT_CLEAR_THIS_TIME);
		for(Object key : map.keySet()){
			if(! doNotClearThisTime.contains(key) && !(key.equals(AjaxContext.FLASH_DO_NOT_CLEAR_THIS_TIME)))
				map.remove(key);
		}
		map.put(AjaxContext.FLASH_DO_NOT_CLEAR_THIS_TIME, new HashSet());
	}
	
	protected JsonAjaxReaction makeJsonAjaxReaction(){
		return new JsonAjaxReactionImpl();
	}
	
	protected JsonAjax makeJsonAjax(){
		return new JsonAjaxImpl();
	}

}
